# Much of this is from anaconda

import crypt
import random
import os
import logging
import sys
#import forest.api
#if not forest.api.DOCUMENTATION:
try:
    import libuser
except ImportError:
    print """Error importing libuser python bindings! Make sure they are
    installed system-wide (eg yum install libuser-python) or available to
    your site packages directory. Virtual environments should be created
    with system-site-packages. """
    sys.exit(-1)

log = logging.getLogger("forest.tools.users")

# These are explained in crypt/crypt-entry.c in glibc's code.  The prefixes
# we use for the different crypt salts:
#     $1$    MD5
#     $5$    SHA256
#     $6$    SHA512
def crypt_password(password, algo=None):
    salts = {'md5': '$1$', 'sha256': '$5$', 'sha512': '$6$'}
    saltlen = 2

    if algo is None:
        algo = 'sha512'

    if algo == 'md5' or algo == 'sha256' or algo == 'sha512':
        saltlen = 16

    saltstr = salts[algo]

    for i in range(saltlen):
        saltstr = saltstr + random.choice(string.letters + string.digits + './')

    return crypt.crypt(password, saltstr)


DEFAULT_LIBUSER = '''
[defaults]
skeleton = %(install_root)s/etc/skel
mailspooldir = %(install_root)s/var/mail
crypt_style = %(algo)s
modules = files shadow
create_modules = files shadow
[files]
directory = %(install_root)s/etc
[shadow]
directory = %(install_root)s/etc
'''
import string
import tempfile

def create_libuser_config(path=None, install_root="", algo="sha512"):

    if path is None:
        (fp, fn) = tempfile.mkstemp(prefix="libuser.")

        Tool.SIGNAL_TOOL_PROGRESS(Users, message='Created temporary libuser.conf at %s' % fn)
        fd = os.fdopen(fp, 'w')
        path = fn
    else:
        Tool.SIGNAL_TOOL_PROGRESS(Users, message='Created libuser.conf at %s' % path)
        fd =open(path,'w')

    buf = DEFAULT_LIBUSER % locals()

    fd.write(buf)
    fd.close()

    os.environ["LIBUSER_CONF"] = path

from forest.registry import register_tool
from forest.tool import Tool

@register_tool('users.Users')
class Users(object):
    def __init__(self, root, path=None):
        self.root = root

        #'/etc/libuser.conf'

        create_libuser_config(None, install_root=root, algo=self.getPassAlgo())

        self.admin = libuser.admin()

    def getPassAlgo(self):
        return 'sha512'

    def set_user_password(self, username, password, isCrypted, lock, algo=None):
        if algo is None:
            algo = self.getPassAlgo()

        user = self.admin.lookupUserByName(username)

        if user is None:
            return

        Tool.SIGNAL_TOOL_PROGRESS(self, message='Setting user %s password.' % username)

        if isCrypted:
            self.admin.setpassUser(user, password, True)
        else:
            self.admin.setpassUser(user, crypt_password(password, algo=algo), True)

        if lock:
            self.admin.lockUser(user)

        self.admin.modifyUser(user)


    def set_root_password(self, password, encrypted=False, lock=False, algo=None):

        return self.set_user_password("root", password, encrypted, lock, algo)


    def createGroup(self, group_name, **kwargs):
        """Create a new user on the system with the given name.  Optional kwargs:

           gid       -- The GID for the new user.  If none is given, the next
                        available one is used.
           root      -- The directory of the system to create the new user
                        in.  homedir will be interpreted relative to this.
                        Defaults to /mnt/sysimage.
        """

        childpid = os.fork()
        root = self.root

        if not childpid:
            if not root in ["","/"]:
                os.chroot(root)
                del(os.environ["LIBUSER_CONF"])

            self.admin = libuser.admin()

            try:
                if self.admin.lookupGroupByName(group_name):
                    os._exit(1)

                groupEnt = self.admin.initGroup(group_name)

                if kwargs.get("gid", -1) >= 0:
                    groupEnt.set(libuser.GIDNUMBER, kwargs["gid"])

                self.admin.addGroup(groupEnt)
                os._exit(0)
            except Exception as e:
                log.critical("Error when creating new group: %s" % str(e))
                os._exit(1)

        try:
            (pid, status) = os.waitpid(childpid, 0)
        except OSError as e:
            log.critical("exception from waitpid while creating a group: %s %s" % (e.errno, e.strerror))
            return False

        if os.WIFEXITED(status) and (os.WEXITSTATUS(status) == 0):
            return True
        else:
            return False

    def createUser (self, user_name, *args, **kwargs):
        """Create a new user on the system with the given name.  Optional kwargs:

           algo      -- The password algorithm to use in case isCrypted=True.
                        If none is given, the cryptPassword default is used.
           gecos     -- The GECOS information (full name, office, phone, etc.).
                        Defaults to "".
           groups    -- A list of existing group names the user should be
                        added to.  Defaults to [].
           homedir   -- The home directory for the new user.  Defaults to
                        /home/<name>.
           isCrypted -- Is the password kwargs already encrypted?  Defaults
                        to False.
           lock      -- Is the new account locked by default?  Defaults to
                        False.
           password  -- The password.  See isCrypted for how this is interpreted.
           root      -- The directory of the system to create the new user
                        in.  homedir will be interpreted relative to this.
                        Defaults to /mnt/sysimage.
           shell     -- The shell for the new user.  If none is given, the
                        libuser default is used.
           uid       -- The UID for the new user.  If none is given, the next
                        available one is used.
        """
        childpid = os.fork()
        root =  self.root

        if not childpid:
            if not root in ["","/"]:
                os.chroot(root)
                del(os.environ["LIBUSER_CONF"])

            self.admin = libuser.admin()

            try:
                if self.admin.lookupUserByName(user_name):
                    os._exit(1)

                userEnt = self.admin.initUser(user_name)
                groupEnt = self.admin.initGroup(user_name)

                grpLst = filter(lambda grp: grp,
                                map(lambda name: self.admin.lookupGroupByName(name), kwargs.get("groups", [])))
                userEnt.set(libuser.GIDNUMBER, [groupEnt.get(libuser.GIDNUMBER)[0]] +
                            map(lambda grp: grp.get(libuser.GIDNUMBER)[0], grpLst))

                if kwargs.get("homedir", False):
                    userEnt.set(libuser.HOMEDIRECTORY, kwargs["homedir"])
                else:
                    iutil.mkdirChain(root+'/home')
                    userEnt.set(libuser.HOMEDIRECTORY, "/home/" + user_name)

                if kwargs.get("shell", False):
                    userEnt.set(libuser.LOGINSHELL, kwargs["shell"])

                if kwargs.get("uid", -1) >= 0:
                    userEnt.set(libuser.UIDNUMBER, kwargs["uid"])

                if kwargs.get("gecos", False):
                    userEnt.set(libuser.GECOS, kwargs["gecos"])

                self.admin.addUser(userEnt, mkmailspool=kwargs.get("mkmailspool", True))
                self.admin.addGroup(groupEnt)

                if kwargs.get("password", False):
                    if kwargs.get("isCrypted", False):
                        password = kwargs["password"]
                    else:
                        password = cryptPassword(kwargs["password"], algo=kwargs.get("algo", None))

                    self.admin.setpassUser(userEnt, password, True)

                if kwargs.get("lock", False):
                    self.admin.lockUser(userEnt)

                # Add the user to all the groups they should be part of.
                grpLst.append(self.admin.lookupGroupByName(user_name))
                for grp in grpLst:
                    grp.add(libuser.MEMBERNAME, user_name)
                    self.admin.modifyGroup(grp)

                os._exit(0)
            except Exception as e:
                log.critical("Error when creating new user: %s" % str(e))
                os._exit(1)

        try:
            (pid, status) = os.waitpid(childpid, 0)
        except OSError as e:
            log.critical("exception from waitpid while creating a user: %s %s" % (e.errno, e.strerror))
            return False

        if os.WIFEXITED(status) and (os.WEXITSTATUS(status) == 0):
            return True
        else:
            return False

    def checkUserExists(self, username, root="/mnt/sysimage"):
        childpid = os.fork()

        if not childpid:
            if not root in ["","/"]:
                os.chroot(root)
                del(os.environ["LIBUSER_CONF"])

            self.admin = libuser.admin()

            try:
                if self.admin.lookupUserByName(username):
                    os._exit(0)
            except Exception as e:
                log.critical("Error when searching for user: %s" % str(e))
            os._exit(1)

        try:
            (pid, status) = os.waitpid(childpid, 0)
        except OSError as e:
            log.critical("exception from waitpid while creating a user: %s %s" % (e.errno, e.strerror))
            return False

        if os.WIFEXITED(status) and (os.WEXITSTATUS(status) == 0):
            return True
        else:
            return False


    def write(self, path='installroot/etc/libuser.conf'):
        # make sure crypt_style in libuser.conf matches the salt we're using
        create_libuser_config(path, install_root=self.root, algoname=self.getPassAlgo())

        # User should already exist, just without a password.
        self.setRootPassword(algo=self.getPassAlgo())
